﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace MidasGtsExporter
{
	public class GtsFpnDataReader
	{
		public SortedList<int, Node> NodeList { get; } = new SortedList<int, Node>();
		public SortedList<int, Elem> Elem3DList { get; } = new SortedList<int, Elem>();
		public SortedList<int, Elem> Elem2DList { get; } = new SortedList<int, Elem>();
		public SortedList<int, Elem> Elem1DList { get; } = new SortedList<int, Elem>();

		public SortedList<int, List<int>> NodeGroups { get; } = new SortedList<int, List<int>>();
		public SortedList<int, List<int>> ElemGroups { get; private set; }

		public SortedList<int, List<int>> Elem3DGroups { get; } = new SortedList<int, List<int>>();
		public SortedList<int, List<int>> Elem2DGroups { get; } = new SortedList<int, List<int>>();
		public SortedList<int, List<int>> Elem1DGroups { get; } = new SortedList<int, List<int>>();

		public Dictionary<int, string> NodeGroupNames { get; private set; }
		public Dictionary<int, string> ElemGroupNames { get; private set; }

		private static readonly string[] Elem3DTypes =
		{
			"HEXA", // 六面体单元
			"PRISM", // 三棱柱单元
			"PYRAM", // 金字塔形单元
			"TETRA", // 四面体单元
		};

		private static readonly string[] Elem2DTypes =
		{
			"RECT", // 矩形面单元
			"TRIA", // 三角形面单元
		};

		private static readonly string[] Elem1DTypes =
		{
			"LINE", // 线单元
		};

		private static readonly string[] Keywords =
		{
			"HEXA",
			"PRISM",
			"PYRAM",
			"TETRA",

			"RECT",
			"TRIA",

			"LINE",

			"NODE",

			"MSET ",
			"MSETE",
			"MSETN",
		};

		private static readonly Dictionary<string, int> ElemInfo = new Dictionary<string, int>()
		{
			{"HEXA", 8},
			{"PRISM", 6},
			{"PYRAM", 5},
			{"TETRA", 4},

			{"RECT", 4},
			{"TRIA", 3},

			{"LINE", 2},
		};

		/// <summary>
		/// 读取并解析Midas Gts FPN文件的内容。
		/// 
		/// Midas Gts中节点和单元的分组均依据“网格组”进行分组；
		/// 每个分组可能同时包含节点和单元；
		/// 一个单元都属于某一个分组，并且同时只能属于一个分组，
		/// 如果单独定义一个单元分组，则选中的单元将从其他分组中排除；
		/// 节点可以同时属于多个分组，因此可以单独定义多个节点分组。
		/// </summary>
		/// <param name="filePath"></param>
		public void ReadData(string filePath)
		{
			Debug.Assert(IO.IsFileExist(filePath));

			//
			// Read file contents
			//
			var allLines = File.ReadAllLines(filePath);
			Debug.Assert(allLines != null && allLines.Length > 0);
			if (allLines.Length <= 0)
				return;

			//
			// Preprocessing
			//
			var keywordBlocks = new List<KeywordBlock>();
			for (var i = 0; i < allLines.Length; i++)
			{
				var line = allLines[i].Trim();
				if (!Keywords.Any(kw =>
					line.StartsWith(kw, StringComparison.OrdinalIgnoreCase)))
					continue;

				var tokens = line.Split(',');
				var keywordBlock = new KeywordBlock()
				{
					Keyword = tokens[0].Trim(),
					BlockData = new List<string>(tokens.Skip(1)) // Skip the keyword token
				};

				while (i + 1 < allLines.Length)
				{
					var nextLine = allLines[i + 1].Trim();
					if (!nextLine.StartsWith(","))
						break;

					tokens = nextLine.Split(',');
					keywordBlock.BlockData.AddRange(tokens.Skip(1)); // Skip the first empty item
					i++;
				}

				keywordBlocks.Add(keywordBlock);
			}

			//
			// Parse node, element, group data
			//
			var allGroupNames = new SortedList<int, string>();
			var allElemGroups = new SortedList<int, List<int>>();
			foreach (var block in keywordBlocks)
			{
				if (IsNodeKeywordBlock(block))
				{
					var node = ParseNodeKeywordBlock(block);
					NodeList.Add(node.Id, node);
				}
				else if (IsElem3DKeywordBlock(block))
				{
					var elem = ParseElem3DKeywordBlock(block);
					Elem3DList.Add(elem.Id, elem);
				}
				else if (IsElem2DKeywordBlock(block))
				{
					var elem = ParseElem2DKeywordBlock(block);
					Elem2DList.Add(elem.Id, elem);
				}
				else if (IsElem1DKeywordBlock(block))
				{
					var elem = ParseElem1DKeywordBlock(block);
					Elem1DList.Add(elem.Id, elem);
				}
				else if (IsGroupNameKeywordBlock(block))
				{
					var g = ParseGroupNameKeywordBlock(block);
					if (g.Key > 1) // 忽略"默认网格组" (MeshSetId = 1)
					{
						allGroupNames.Add(g.Key, g.Value);
					}
				}
				else if (IsElemGroupKeywordBlock(block))
				{
					var g = ParseElemGroupKeywordBlock(block);
					if (g.Key > 1 && g.Value.Count>0) // 忽略"默认网格组"(MeshSetId = 1)和空单元组
					{
						allElemGroups.Add(g.Key, g.Value);
					}
				}
			}

			//
			// Elem groups, 3D、2D、1D单元必须分开分组，不能放到同一个分组里
			//
			foreach (var g in allElemGroups)
			{
				if (Elem3DList.ContainsKey(g.Value[0]))
				{
					Elem3DGroups.Add(g.Key, g.Value);
				}
				else if (Elem2DList.ContainsKey(g.Value[0]))
				{
					Elem2DGroups.Add(g.Key, g.Value);
				}
				else if (Elem1DList.ContainsKey(g.Value[0]))
				{
					Elem1DGroups.Add(g.Key, g.Value);
				}
			}

			ElemGroups = new SortedList<int, List<int>>(Elem3DGroups
				.Concat(Elem2DGroups)
				.Concat(Elem1DGroups)
				.ToDictionary(kv => kv.Key, kv => kv.Value));
			Debug.Assert(ElemGroups.Select(g => g.Key).Distinct().Count()
			             == ElemGroups.Count);

			//
			// Elem group names
			//
			Debug.Assert(ElemGroups.All(g => allGroupNames.ContainsKey(g.Key)));
			ElemGroupNames = ElemGroups.ToDictionary(g => g.Key, g => allGroupNames[g.Key]);

			//
			// Node groups
			//
			foreach (var block in keywordBlocks)
			{
				if (!IsNodeGroupKeywordBlock(block))
					continue;

				var tokens = block.BlockData;
				var groupId = int.Parse(tokens[0]);
				if (groupId > 1 && ElemGroups.ContainsKey(groupId)) // 忽略"默认网格组"(MeshSetId = 1)和与单元组ID相同的组
				{
					var g = ParseNodeGroupKeywordBlock(block);
					if (g.Value.Count > 0) // 忽略空节点组
					{
						NodeGroups.Add(g.Key, g.Value);
					}
				}
			}

			//
			// Node group names
			//
			NodeGroupNames = NodeGroups
				.ToDictionary(g => g.Key, g => allGroupNames[g.Key]);
		}

		#region Data Line parser

		private static bool IsNodeKeywordBlock(KeywordBlock keywordBlock)
		{
			return keywordBlock.Keyword == "NODE";
		}

		private static bool IsElem3DKeywordBlock(KeywordBlock keywordBlock)
		{
			return Elem3DTypes.Any(s => keywordBlock.Keyword == s);
		}

		private static bool IsElem2DKeywordBlock(KeywordBlock keywordBlock)
		{
			return Elem2DTypes.Any(s => keywordBlock.Keyword == s);
		}

		private static bool IsElem1DKeywordBlock(KeywordBlock keywordBlock)
		{
			return Elem1DTypes.Any(s => keywordBlock.Keyword == s);
		}

		private static bool IsGroupNameKeywordBlock(KeywordBlock keywordBlock)
		{
			return keywordBlock.Keyword == "MSET";
		}

		private static bool IsNodeGroupKeywordBlock(KeywordBlock keywordBlock)
		{
			return keywordBlock.Keyword == "MSETN";
		}

		private static bool IsElemGroupKeywordBlock(KeywordBlock keywordBlock)
		{
			return keywordBlock.Keyword == "MSETE";
		}

		private static Node ParseNodeKeywordBlock(KeywordBlock keywordBlock)
		{
			Debug.Assert(IsNodeKeywordBlock(keywordBlock));
			var tokens = keywordBlock.BlockData;
			var id = int.Parse(tokens[0]);
			var x = double.Parse(tokens[1]);
			var y = double.Parse(tokens[2]);
			var z = double.Parse(tokens[3]);
			return new Node(id, x, y, z);
		}

		private static Elem ParseElem3DKeywordBlock(KeywordBlock keywordBlock)
		{
			Debug.Assert(IsElem3DKeywordBlock(keywordBlock));
			var tokens = keywordBlock.BlockData;
			var id = int.Parse(tokens[0]);
			const int skip = 2;
			var nodeIds = tokens.Skip(skip)
				.Take(ElemInfo[keywordBlock.Keyword])
				.Select(int.Parse)
				.ToList();
			Debug.Assert(4 <= nodeIds.Count && nodeIds.Count <= 8);
			return new Elem(id, nodeIds);
		}

		private static Elem ParseElem2DKeywordBlock(KeywordBlock keywordBlock)
		{
			Debug.Assert(IsElem2DKeywordBlock(keywordBlock));
			var tokens = keywordBlock.BlockData;
			var id = int.Parse(tokens[0]);
			const int skip = 2;
			var nodeIds = tokens.Skip(skip)
				.Take(ElemInfo[keywordBlock.Keyword])
				.Select(int.Parse)
				.ToList();
			Debug.Assert(3 <= nodeIds.Count && nodeIds.Count <= 4);
			return new Elem(id, nodeIds);
		}

		private static Elem ParseElem1DKeywordBlock(KeywordBlock keywordBlock)
		{
			Debug.Assert(IsElem1DKeywordBlock(keywordBlock));
			var tokens = keywordBlock.BlockData;
			var id = int.Parse(tokens[0]);
			const int skip = 2;
			var nodeIds = tokens.Skip(skip)
				.Take(ElemInfo[keywordBlock.Keyword])
				.Select(int.Parse)
				.ToList();
			Debug.Assert(nodeIds.Count == 2);
			return new Elem(id, nodeIds);
		}

		private static KeyValuePair<int, string> ParseGroupNameKeywordBlock(KeywordBlock keywordBlock)
		{
			Debug.Assert(IsGroupNameKeywordBlock(keywordBlock));
			var tokens = keywordBlock.BlockData;
			var id = int.Parse(tokens[0]);
			var groupName = tokens[1].Trim();
			return new KeyValuePair<int, string>(id, groupName);
		}

		private static KeyValuePair<int, List<int>> ParseElemGroupKeywordBlock(KeywordBlock keywordBlock)
		{
			Debug.Assert(IsElemGroupKeywordBlock(keywordBlock));
			var tokens = keywordBlock.BlockData;
			var groupId = int.Parse(tokens[0]);
			var itemCount = int.Parse(tokens[1]);
			const int skip = 8;
			var itemIds = tokens.Skip(skip)
				.Take(itemCount)
				.Select(int.Parse)
				.ToList();
			Debug.Assert(itemIds.All(id => id > 0));
			return new KeyValuePair<int, List<int>>(groupId, itemIds);
		}

		private static KeyValuePair<int, List<int>> ParseNodeGroupKeywordBlock(KeywordBlock keywordBlock)
		{
			Debug.Assert(IsNodeGroupKeywordBlock(keywordBlock));
			var tokens = keywordBlock.BlockData;
			var groupId = int.Parse(tokens[0]);
			var itemCount = int.Parse(tokens[1]);
			const int skip = 8;
			var itemIds = tokens.Skip(skip)
				.Take(itemCount)
				.Select(int.Parse)
				.ToList();
			Debug.Assert(itemIds.All(id => id > 0));
			return new KeyValuePair<int, List<int>>(groupId, itemIds);
		}

		#endregion

		#region Helper classes

		public class KeywordBlock
		{
			public string Keyword { get; set; }
			public List<string> BlockData { get; set; }
		}

		#endregion
	}
}